#pragma once

#include <string>
#include <unordered_map>

enum class SyntaxKind : int
{
    Unkown,
    Whitespace, // ' ','\t','\r','\n';
    Comments,   //  //xxxx , /*xxxxx*/;

    OpenParenthesis,  //(;
    CloseParenthesis, //);

    OpenBrace,  //{;
    CloseBrace, //};

    OpenBracket,  //[;
    CloseBracket, //];

    Dot,       //.;
    Hat,       //^;
    Coma,      //,;
    Colon,     //:;
    Tilde,     //~;
    Semicolon, //;;

    Plus,  //+;
    Minus, //-;
    Star,  //*;
    Slash, // div;
    Mod,   //%;// Percent;

    Bang,               // ! ;
    Pipe,               // | ;
    PipePipe,           // ||;
    Ampersand,          // & ;
    AmpersandAmpersand, // &&;

    Less,            // < ;
    LessOrEquals,    // <=;
    Greater,         // > ;
    GreaterOrEquals, // >=;
    Equals,          // ==;
    BangEquals,      // !=;

    Assign, //  =;

    Eof,

    Number,     // 0-9;
    String,     // string "xxxx  \"  "
    Identifier, //_a-zA-Z + _a-zA-Z0-9;

    // Keyword;
    Do,
    If,
    Else,
    While,
    Switch,
    Case,
    For,
    To,
    Default,
    Break,
    Countinue,
    True,
    False,
    Let,
    Var,

    // Nodes;
    CompilationUnit,
    ElseClause,

    // statement;
    IfStatement,
    ForStatement,
    BlockStatement,
    WhileStatement,
    ExpressionStatement,
    VariableDeclaration,

    // Expression;
    NameExpression,
    LiteralExpression,
    UnaryExpression,
    BinaryExpression,
    AssignmentExpression,
    ParenthesizedExpression,
};
#define STR(s) #s
#define MakeKindNameToken(kind)            \
    {                                      \
        SyntaxKind::kind, STR(kind##Token) \
    }

#define MakeKindName(kind)          \
    {                               \
        SyntaxKind::kind, STR(kind) \
    }

static std::unordered_map<SyntaxKind, std::string> AllSyntaxKindName = {
    MakeKindNameToken(Unkown),
    MakeKindNameToken(Whitespace),
    MakeKindNameToken(Comments),

    MakeKindNameToken(OpenParenthesis),
    MakeKindNameToken(CloseParenthesis),
    MakeKindNameToken(OpenBrace),
    MakeKindNameToken(CloseBrace),
    MakeKindNameToken(OpenBracket),
    MakeKindNameToken(CloseBracket),

    MakeKindNameToken(Dot),
    MakeKindNameToken(Hat),
    MakeKindNameToken(Coma),
    MakeKindNameToken(Colon),
    MakeKindNameToken(Tilde),
    MakeKindNameToken(Semicolon),

    MakeKindNameToken(Plus),
    MakeKindNameToken(Minus),
    MakeKindNameToken(Star),
    MakeKindNameToken(Slash),
    MakeKindNameToken(Mod),

    MakeKindNameToken(Bang),
    MakeKindNameToken(Ampersand),
    MakeKindNameToken(AmpersandAmpersand),
    MakeKindNameToken(Pipe),
    MakeKindNameToken(PipePipe),

    MakeKindNameToken(Less),
    MakeKindNameToken(LessOrEquals),
    MakeKindNameToken(Greater),
    MakeKindNameToken(GreaterOrEquals),
    MakeKindNameToken(Equals),
    MakeKindNameToken(BangEquals),

    MakeKindNameToken(Assign),
    MakeKindNameToken(Eof),

    MakeKindNameToken(Number),
    MakeKindNameToken(String),
    MakeKindNameToken(Identifier),

    // keyword;
    MakeKindNameToken(Do),
    MakeKindNameToken(If),
    MakeKindNameToken(Else),
    MakeKindNameToken(While),
    MakeKindNameToken(Switch),
    MakeKindNameToken(Case),
    MakeKindNameToken(For),
    MakeKindNameToken(To),
    MakeKindNameToken(Default),
    MakeKindNameToken(Break),
    MakeKindNameToken(True),
    MakeKindNameToken(False),
    MakeKindNameToken(Countinue),

    // Nodes;
    MakeKindName(CompilationUnit),
    MakeKindName(ElseClause),

    // statement;
    MakeKindName(IfStatement),
    MakeKindName(ForStatement),
    MakeKindName(BlockStatement),
    MakeKindName(WhileStatement),
    MakeKindName(ExpressionStatement),
    MakeKindName(VariableDeclaration),

    // Expression;
    MakeKindName(NameExpression),
    MakeKindName(UnaryExpression),
    MakeKindName(BinaryExpression),
    MakeKindName(LiteralExpression),
    MakeKindName(ParenthesizedExpression),
};

#undef STR
#undef MakeKindName
#undef MakeKindNameToken

class SyntaxKindName
{
public:
    static std::string GetTokenName(SyntaxKind kind)
    {
        auto it = AllSyntaxKindName.find(kind);

        if (it != AllSyntaxKindName.end())
        {
            return it->second;
        }
        return "";
    }
};
